using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Math;
using JigLibX.Geometry;
using JigLibX.Utils;

namespace QuickStart.Physics.JigLibX
{
    /// <summary>
    /// An implementation of the <see cref="IPhysicsActor"/> interface using JigLibX.
    /// </summary>
    public class JigLibXActor : IPhysicsActor
    {
        private Body jigLibXBody;
        private CollisionSkin jigLibXSkin;
        private List<ShapeDesc> shapes;

        /// <summary>
        /// Constructs a new physics actor.
        /// </summary>
        /// <param name="desc">Descriptor for the actor.</param>
        internal JigLibXActor(ActorDesc desc)
        {
            shapes = desc.Shapes;
            bool applyCOM = true;

            // Construct the JigLibX bosy/skin for this actor.
            jigLibXBody = new Body();
            jigLibXSkin = new CollisionSkin(jigLibXBody);

            // Build all shapes that make up the actor.
            for(int i = 0; i < desc.Shapes.Count; ++i)
            {
                ShapeDesc shapeDesc = desc.Shapes[i];

                // The basic concept is the same for all shapes: create JigLibX shape of proper size and bind it to the skin.
                // TODO: Implement materials.

                if(shapeDesc is BoxShapeDesc)
                {
                    BoxShapeDesc boxDesc = shapeDesc as BoxShapeDesc;
                    Box box = new Box(-boxDesc.Extents, desc.Orientation, boxDesc.Extents * 2.0f);
                    jigLibXSkin.AddPrimitive(box, (int)MaterialTable.MaterialID.UserDefined, new MaterialProperties(0.1f, 0.5f, 0.5f));
                }
                else if(shapeDesc is SphereShapeDesc)
                {
                    SphereShapeDesc sphereDesc = shapeDesc as SphereShapeDesc;
                    Sphere sphere = new Sphere(Vector3.Zero, sphereDesc.Radius);
                    jigLibXSkin.AddPrimitive(sphere, (int)MaterialTable.MaterialID.UserDefined, new MaterialProperties(0.5f, 0.5f, 0.5f));
                }
                else if(shapeDesc is HeightFieldShapeDesc)
                {
                    // For height fields, we need to copy the data into an Array2D.
                    HeightFieldShapeDesc heightFieldDesc = shapeDesc as HeightFieldShapeDesc;
                    Array2D field = new Array2D(heightFieldDesc.HeightField.GetUpperBound(0), heightFieldDesc.HeightField.GetUpperBound(1));

                    for(int x = 0; x < heightFieldDesc.HeightField.GetUpperBound(0); x++)
                    {
                        for(int z = 0; z < heightFieldDesc.HeightField.GetUpperBound(1); z++)
                        {
                            field.SetAt(x, z, heightFieldDesc.HeightField[x, z]);
                        }
                    }

                    Heightmap heightmap = new Heightmap(field, heightFieldDesc.SizeX / 2.0f, heightFieldDesc.SizeZ / 2.0f, heightFieldDesc.SizeX / heightFieldDesc.HeightField.GetLength(0), heightFieldDesc.SizeZ / heightFieldDesc.HeightField.GetLength(1));
                    jigLibXSkin.AddPrimitive(heightmap, (int)MaterialTable.MaterialID.UserDefined, new MaterialProperties(0.5f, 0.9f, 0.8f));

                    applyCOM = false;
                }
                else if(shapeDesc is TriangleMeshShapeDesc)
                {
                    throw new Exception("Not implemented.");
                }
                else
                {
                    throw new Exception("Bad shape.");
                }
            }

            // Finalize the body/skin and set center-of-mass.
            PrimitiveProperties primitiveProperties = new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, desc.Density);

            float junk;
            Vector3 com;
            Matrix it, itCoM;

            jigLibXSkin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
            jigLibXBody.BodyInertia = itCoM;
            jigLibXBody.Mass = junk;

            jigLibXBody.CollisionSkin = jigLibXSkin;

            jigLibXBody.MoveTo(desc.Position /*+ com*/, desc.Orientation);

            if(applyCOM)
            {
                jigLibXSkin.ApplyLocalTransform(new Transform(-com /*Vector3.Zero*/, Matrix.Identity));
            }

            // Let JigLibX know this body should be a part of the simulation.
            jigLibXBody.EnableBody();
            
            jigLibXBody.Immovable = !desc.Dynamic;
        }

        /// <summary>
        /// Releases all unmanaged resources for the actor.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Gets/sets the actor's density.
        /// </summary>
        public float Density
        {
            get
            {
                throw new Exception("Not implemented");
            }
            set
            {
                // Reset the body's inertia and mass with the new density value.
                PrimitiveProperties primitiveProperties = new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, value);

                float junk; 
                Vector3 com; 
                Matrix it, itCoM;

                jigLibXSkin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
                jigLibXBody.BodyInertia = itCoM;
                jigLibXBody.Mass = junk;
            }
        }

        /// <summary>
        /// Gets/sets the actor's mass.
        /// </summary>
        public float Mass
        {
            get
            {
                throw new Exception("Not implemented");
            }
            set
            {
                // Reset the body's inertia and mass with the new mass value.
                PrimitiveProperties primitiveProperties = new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Mass, value);

                float junk;
                Vector3 com;
                Matrix it, itCoM;

                jigLibXSkin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
                jigLibXBody.BodyInertia = itCoM;
                jigLibXBody.Mass = junk;
            }
        }

        /// <summary>
        /// Gets/sets the actor's position.
        /// </summary>
        public Vector3 Position
        {
            get
            {
                return jigLibXBody.Position;
            }
            set
            {
                throw new Exception("Not implemented");
            }
        }

        /// <summary>
        /// Gets/sets the actor's orientation.
        /// </summary>
        public Matrix Orientation
        {
            get
            {
                return jigLibXBody.Orientation;
            }
            set
            {
                throw new Exception("Not implemented");
            }
        }

        /// <summary>
        /// Gets the list of shapes that make up the actor.
        /// </summary>
        public List<ShapeDesc> Shapes
        {
            get 
            {
                return shapes;
            }
        }
    }
}
